#include <stdio.h>
#include <stdlib.h>

#include "mem.h"

/**
 * as malloc, but returns (MEMBYTE*)
 * @param size
 */
MEMBYTE * memalloc(const int & size)
{
	return (MEMBYTE*)malloc(size);
}

/**
 * @param mem which memory to free
 * @param size (unused) how much if it there is to free
 */
void memfree(MEMBYTE * mem, const int & size)
{
	free(mem);
}

/**
 * @param src "source memory" what to copy
 * @param dest "destination memory" where to copy to
 * @param len how much to copy
 * @return pointer to dest (modified after copy)
 */
MEMBYTE * memcopy(const MEMBYTE * src, MEMBYTE * dest, const int & len)
{
	int i;
	for(i = 0; i < len; ++i)
	{
		dest[i] = src[i];
	}
	return dest;
}

/**
 * @param mem what data to copy
 * @param len how much data to copy
 * @param newSize new size of allocation
 */
MEMBYTE * memrealloc(MEMBYTE * mem, const int & len, const int & newSize)
{
	MEMBYTE * newMem = memalloc(newSize);
	if(len)
	{
		memcopy(mem, newMem, MIN(len, newSize));
		memfree(mem, len);
	}
	return newMem;
}

/**
 * @param src0 first memory source
 * @param src1 second memory source
 * @param length how much memory to compare
 * @return (src0[i:length] - src1[i:length]). difference between last non-equal MEMBYTEs. 0 if the memory values are identical.
 */
int memcompare(const MEMBYTE * src0, const MEMBYTE * src1, const int & length)
{
	if(src0 != src1)
	{
		int i;
		for(i = 0; i < length; ++i)
		{
			if(src0[i] != src1[i])
			{
				return src0[i] - src1[i];
			}
		}
	}
	return 0;
}

/**
 * @param src0 first memory source
 * @param len0 length of first memory source
 * @param src1 second memory source
 * @param len1 length of second memory source
 * @return (src0[i:length] - src1[i:length]). difference between last non-equal MEMBYTEs. 0 if the memory values are identical. value of MEMBYTE in src0 if src0 is longer. negative value of MEMBYTE in src1 if src1 is longer
 */
int memcompare2(const MEMBYTE * src0, const int & len0, const MEMBYTE * src1, const int & len1)
{
	if(len0 > len1)
	{
		return src0[len1];
	}
	else if(len0 < len1)
	{
		return -src1[len0];
	}
	return memcompare(src0, src1, len1);
}

/**
 * @param haystack where to search.
 * @param lenHaystack size of the haystack
 * @param needle what to search for.
 * @param lenNeedle size of the needle
 * @return the location of the needle found in haystack in memory
 */
MEMBYTE * memmem(MEMBYTE * haystack, const int & lenHaystack, const int & startHaystack, MEMBYTE * needle, const int & lenNeedle)
{
	int i, difference;
	for(i = 0; i < lenHaystack; ++i)
	{
		difference = memcompare(haystack+i, needle, lenNeedle);
		if(!difference)
		{
			return haystack+i;
		}
	}
	return NULL;
}

/**
 * @param mem0 where to get first data
 * @param len0 how much data is in the first data block
 * @param mem1 where to get second data
 * @param len1 how much data is in the second data block
 * @return a newly allocated block of memory that is len0+len1 MEMBYTEs big, containing mem0 and mem1 consecutivley (in that order)
 */
MEMBYTE * memconcat(const MEMBYTE * mem0, const int & len0, const MEMBYTE * mem1, const int & len1)
{
	// create the new heap
	int memsum = len0 + len1;
	MEMBYTE * memadd = memalloc(memsum);
	if(len0)
	{
		// copy mem0 data into memadd
		memcopy(mem0, memadd, len0);
	}
	if(len1)
	{
		// copy mem1 data into memadd (after mem0)
		memcopy(mem1, memadd+len0, len1);
	}
	return memadd;
}

/**
 * @param filename the name of the file to load completley into memory
 * @param len a pointer to the integer where to put the file's size
 * @return allocated memory. 0 if data buffer was not correctly allocated
 */
MEMBYTE * memFromFile(const char * filename, int * len)
{
	FILE * pFile;
	int read = -1;
	fopen_s(&pFile, filename, "rb");
	//pFile = fopen (filename, "rb");
	MEMBYTE * buffer = 0;
	if (pFile)
	{
		// check to see if there is enough data to aquire a length
		if(fseek (pFile , 0 , SEEK_END) == 0)
		{
			// grab the total file size
			long lSize = ftell (pFile);
			rewind (pFile);
		
			// allocate memory to contain the whole file:
			buffer = memalloc(sizeof(MEMBYTE)*lSize);
			if (buffer)
			{
				read = fread (buffer,1,lSize,pFile);
				if (read != lSize)
				{
					read = -1;
				}
			}
		}
		fclose(pFile);
	}
	(*len) = read;
	return buffer;
}

/**
 * @param data where to write to write from
 * @param length how much to write
 * @param filename where to write to (file in file system)
 */
int memToFile(const MEMBYTE * data, const int & length, const char * filename)
{
	{
		FILE *fp;
		int written = 0;
		fopen_s(&fp, filename, "wb");
		if(fp)
		{
			int i;
			for(i = 0; i < length; ++i)
			{
				fwrite(&data[i], sizeof(MEMBYTE), 1, fp);
			}
			written += length;
			if(fflush(fp) == EOF)
			{
				written = -1;
			}
			fclose(fp);
		}
		return written;
	}
}

/**
 * @param haystack which memory block to search
 * @param needle what value to search for
 * @param the index of the first instance of needle in haystack
 */
int memindexOf(const MEMBYTE * haystack, const int & haystackLen, const int & haystackStart, const MEMBYTE & needle)
{
	int i;
	for(i = haystackStart; haystack[i] != needle 
		&& (haystackLen < 0 || i < haystackLen); ++i);
	if(i >= haystackLen)
	{
		i = -1;
	}
	return i;
}

/**
 * @param data memory block to grab data from
 * @param bytes number of MEMBYTEs to read as an integer
 * @return bytes number of MEMBYTEs from data manually typecasted into an int
 */
int bytestoi(const MEMBYTE * data, const int & offset, const int & bytes)
{
	int result = 0, shift = 0, i;
	for(i = 0; i < bytes; ++i)
	{
		result |= data[i+offset] << shift;
		shift += BITS_PER_BYTE;
	}
	return result;
}

/**
 * @param data memory block to grab data from
 * @param offsetBits how many bits to offset
 * @param bits how many bits to read
 * @return bits number of bits from data manually typecasted into an int
 */
int bitstoi(const MEMBYTE * data, const int & a_offsetBits, const int & a_bits)
{
	int offsetBytes = 0;
	int offsetBits = a_offsetBits;
	int bits = a_bits;
	long result = 0;
	if(offsetBits > BITS_PER_BYTE)
	{
		offsetBytes = offsetBits / BITS_PER_BYTE;
		offsetBits %= BITS_PER_BYTE;
	}
	int bitsRead = 0;
	while(bitsRead < bits)
	{
		//// big endian
		//result |= ((data[offsetBytes] >> (BITS_PER_BYTE-offsetBits-1)) & 1) 
		// << ((bitsRead % BITS_PER_BYTE));
		
		// little endian
		int shift = (BITS_PER_BYTE-1-offsetBits);
		result |= ((data[offsetBytes] >> shift) & 1) << shift;

		bitsRead++;
		offsetBits++;
		if(offsetBits > BITS_PER_BYTE)
		{
			bits -= BITS_PER_BYTE;
			bitsRead -= BITS_PER_BYTE;
			offsetBytes++;
			offsetBits = 0;
		}
	}
	return result;
}

void bitsToString(const MEMBYTE * data, const int & a_offsetBits, const int & a_bits, char * str, const int & strSize)
{
	int offsetBytes = 0;
	int offsetBits = a_offsetBits;
	int bits = a_bits;
	//long result = 0;
	if(offsetBits > BITS_PER_BYTE)
	{
		offsetBytes = offsetBits / BITS_PER_BYTE;
		offsetBits %= BITS_PER_BYTE;
	}
	int bitsRead = 0;
	int letter = 0;
	while(bitsRead < bits && letter < strSize)
	{
		//// big endian
		//result |= ((data[offsetBytes] >> (BITS_PER_BYTE-offsetBits-1)) & 1) 
		// << ((bitsRead % BITS_PER_BYTE));
		
		// little endian
		int shift = (BITS_PER_BYTE-1-offsetBits);
		str[letter++] = '0'+((data[offsetBytes] >> shift) & 1);
		//result |= ((data[offsetBytes] >> shift) & 1) << shift;

		bitsRead++;
		offsetBits++;
		if(offsetBits > BITS_PER_BYTE)
		{
			bits -= BITS_PER_BYTE;
			bitsRead -= BITS_PER_BYTE;
			offsetBytes++;
			offsetBits = 0;
		}
	}
}

// TODO make more elegant
void writeBits(const int & a_value, const int & a_numBits, 
			  MEMBYTE * data, const int & a_offsetBits)
{
	int offsetBytes = 0;
	int offsetBits = a_offsetBits;
	int bits = a_numBits;
	if(offsetBits > BITS_PER_BYTE)
	{
		offsetBytes = offsetBits / BITS_PER_BYTE;
		offsetBits %= BITS_PER_BYTE;
	}
	int bitsWritten = 0, totalBitsWritten = 0;
	while(totalBitsWritten < a_numBits)
	{
		// little endian
//		int shift = (BITS_PER_BYTE-1-offsetBits);
		int bit = (a_value << totalBitsWritten) & 1;

		if(bit)
			data[offsetBytes] |= bit << offsetBits;
		else
			data[offsetBytes] %= ~(bit << offsetBits);

		bitsWritten++;
		offsetBits++;
		if(offsetBits > BITS_PER_BYTE)
		{
			bits -= BITS_PER_BYTE;
			bitsWritten -= BITS_PER_BYTE;
			offsetBytes++;
			offsetBits = 0;
		}
	}
}

int isNormalChar(const MEMBYTE b)
{
	return b >= 32 && b < 128;
}

/**
 * prints out memory in a "human readable" form
 * @param bytes where to start printing memory from
 * @param len how many MEMBYTEs to print
 */
void byteArrayOutput(const MEMBYTE * bytes, const int & len)
{
	unsigned char b;
	int inString = 0, i;
	for(i = 0; i < len; ++i)
	{
		b = bytes[i];
		if(isNormalChar(b))
		{
			if(!inString)
			{
				if(i > 0)
				{
					printf(" ");
				}
				printf("\"");
				inString = 1;
			}
			printf("%c", b);
		}
		else
		{
			if(inString)
			{
				printf("\"");
				inString = 0;
			}
			if(i > 0)
			{
				printf(" ");
			}
			printf("%x", b);
		}
	}
}

int STRLEN(const char * a_str)
{
	return (a_str)?memindexOf((MEMBYTE*)a_str, -1, 0, 0):0;
}

int STRCMP(const char * a_str0, const char * a_str1)
{
	return memcompare((MEMBYTE*)a_str0, (MEMBYTE*)a_str1, STRLEN(a_str0)+1);
}

int reverseBits(int & a_i, int & bitDepth)
{
	if(bitDepth >= 8)
	{
		a_i = ((a_i & 0xf0) >> 4) | ((a_i & 0x0f) << 4);
	}
	if(bitDepth >= 4)
	{
		a_i = ((a_i & 0xcc) >> 2) | ((a_i & 0x33) << 2);
	}
	if(bitDepth >= 2)
	{
		a_i = ((a_i & 0xaa) >> 1) | ((a_i & 0x55) << 1); 
	}
	return a_i;
}
